﻿//////////////////////////////////////////////////////////////////
//
// Fabio\Main.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Utilities;
using ZMQ;
using System.Threading;
using System.IO;
using Interfaces;

namespace Fabio
{
   public partial class Main : Form
   {
      public static Logger logger_;
      public Thread listenThread_;
      public Thread[] loaderThreads_;
      public EventWaitHandle stopEvent_;
      private const Int32 MAGIC_HEIGHTS = 0x27258421;
      private const Int32 MAGIC_NORMALS = 0x15354773;
      private const string INPROC_LOADER = "inproc://l";

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      static Main()
      {
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      public Main()
      {
         InitializeComponent();
         stopEvent_ = new EventWaitHandle(false, EventResetMode.ManualReset);
         listenThread_ = new Thread((st) => threadListener(st));
         listenThread_.Name = "Listener";
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void eXitToolStripMenuItem_Click(object sender, EventArgs e)
      {
         Close();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void Main_Load(object sender, EventArgs e)
      {
         // Now safe to initialize the Logger object
         logger_ = Program.Logger;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void Main_FormClosed(object sender, FormClosedEventArgs e)
      {
         stopEvent_.Set();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void Main_FormClosing(object sender, FormClosingEventArgs e)
      {
         try
         {
            Program.Settings.WindowState.Value = WindowState;
            if (FormWindowState.Normal == WindowState)
            {
               Program.Settings.Window.Value = new Rectangle(Left, Top, Width, Height);
            }
            else
            {
               Program.Settings.Window.Value = new Rectangle(0, 0, 0, 0);
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void Main_Shown(object sender, EventArgs e)
      {
         try
         {
            Rectangle client = Program.Settings.Window.Value;
            if ((0 < client.Width) && (0 < client.Height))
            {
               WindowState = Program.Settings.WindowState;
               if (FormWindowState.Normal == WindowState)
               {
                  Left = client.Left;
                  Top = client.Top;
                  Width = client.Width;
                  Height = client.Height;
               }
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void startToolStripMenuItem_Click(object sender, EventArgs e)
      {
         stopEvent_.Reset();
         listenThread_.Start();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="state"></param>
      private void threadListener(object state)
      {
         try
         {
            logger_.DebugFormat("listener thread is starting");
            SafeAsyncInvoker.BeginInvoke(statusStrip1, () =>
            {
               statusListener.ForeColor = SystemColors.ControlText;
               statusListener.Text = "starting";
            });

            using (Context context = new Context(1))
            {
               using (Socket server = context.Socket(SocketType.ROUTER)
                  , loader = context.Socket(SocketType.DEALER))
               {
                  string listen = string.Format("tcp://*:{0}", Program.Settings.ListenPort);
                  server.Bind(listen);
                  loader.Bind(INPROC_LOADER);

                  // Start the loader threads
                  loaderThreads_ = new Thread[Program.Settings.Loaders];
                  for (int index = 0; index < loaderThreads_.Length; ++index)
                  {
                     loaderThreads_[index] = new Thread((st) => threadLoader(st));
                     loaderThreads_[index].Start(context);
                  }

                  // Forward incoming messages to the loader task
                  server.PollInHandler += (socket, eventArgs) => { new ZMessage(socket).Send(loader); };
                  server.PollErrHandler += (socket, eventArgs) => { logger_.DebugFormat("server.PollErrHandler"); };
                  //server.PollOutHandler += (socket, eventArgs) => { logger_.DebugFormat("server.PollOutHandler"); };

                  // Receive messages from the loader task
                  loader.PollInHandler += (socket, eventArgs) => { new ZMessage(socket).Send(server); };
                  loader.PollErrHandler += (socket, eventArgs) => { logger_.DebugFormat("loader.PollErrHandler"); };
                  //loader.PollOutHandler += (socket, eventArgs) => { logger_.DebugFormat("loader.PollOutHandler"); };

                  SafeAsyncInvoker.BeginInvoke(statusStrip1, () =>
                  {
                     statusListener.ForeColor = SystemColors.ControlText;
                     statusListener.Text = "listener ready";
                  });

                  bool done = false;
                  while (!done)
                  {
                     if (stopEvent_.WaitOne(0))
                     {
                        logger_.DebugFormat("listener thread detected stop event");
                        done = true;
                     }
                     else
                     {
                        Context.Poller(1000, loader, server);
                     }
                  }
                  logger_.DebugFormat("listener thread is stopping");

                  SafeAsyncInvoker.BeginInvoke(statusStrip1, () =>
                  {
                     statusListener.ForeColor = SystemColors.ControlText;
                     statusListener.Text = "stopping";
                  });

                  // Wait for all of the loader threads to stop
                  int count = 0;
                  DateTime lastMsg = DateTime.Now;
                  while (0 < (count = loaderThreads_.Count(thread => thread.IsAlive)))
                  {
                     Thread.Sleep(100);
                     if (3 < (DateTime.Now - lastMsg).TotalSeconds)
                     {
                        logger_.DebugFormat("still waiting for {0} loader threads to shut down", count);
                     }
                  }
               }
            }
            SafeAsyncInvoker.BeginInvoke(statusStrip1, () =>
            {
               statusListener.ForeColor = Color.Red;
               statusListener.Text = "not listening";
            });

            logger_.DebugFormat("listener thread is exiting");
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="state"></param>
      private void threadLoader(object state)
      {
         try
         {
            logger_.DebugFormat("loader thread is starting");

            Context context = state as Context;

            using (Socket worker = context.Socket(SocketType.DEALER))
            {
               worker.Connect(INPROC_LOADER);
               worker.PollInHandler += (socket, eventArgs) =>
               {
                  ZMessage requestMessage = new ZMessage(socket);
                  ZMessage tileMessage = loadTile(requestMessage);
                  if (null != tileMessage)
                  {
                     tileMessage.Send(worker);
                  }
                  else
                  {
                     worker.Send();
                  }
               };

               bool done = false;
               while (!done)
               {
                  if (stopEvent_.WaitOne(0))
                  {
                     logger_.DebugFormat("loader thread detected stop event");
                     done = true;
                  }
                  else
                  {
                     Context.Poller(1000, worker);
                  }
               }
               logger_.DebugFormat("loader thread is stopping");
            }
            logger_.DebugFormat("loader thread is exiting");
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="tileRequest"></param>
      private ZMessage loadTile(ZMessage tileRequest)
      {
         BinaryReader reader;
         ILocation location = Program.Loader.Create<ILocation>();
         using (MemoryStream stream = new MemoryStream(tileRequest.Body))
         {
            reader = new BinaryReader(stream);
            byte code = reader.ReadByte();
            if (1 != code)
            {
               logger_.DebugFormat("loadTile() ignoring code {0} - not a tile request", code);
               return null;
            }

            location.Latitude = reader.ReadSingle();
            location.Longitude = reader.ReadSingle();
         }

         logger_.DebugFormat("tile requested '{0}'", location);
         string basename = Path.Combine(Program.Settings.CellFolder,
            string.Concat(
               (Hemisphere.North == location.LatitudeHemisphere) ? "N" : "S",
               Math.Abs(location.LatitudeDegrees).ToString("00"),
               (Hemisphere.East == location.LongitudeHemisphere) ? "E" : "W",
               Math.Abs(location.LongitudeDegrees).ToString("000")
               )
            );

         byte[] buffer = new byte[
              (512 * 2 + 60 * 60)      // Height conversion table plus the height codes
            + (60 * 60 * 2 * 2)        // Two face normals encoded in one 16-bit word per vector
            + (61 * 61 * 2)            // One vertex normal per point, encoded in one 16-bit word per vector
            ];

         string filename = Path.ChangeExtension(basename, ".heights");
         using (FileStream fileStream = new FileStream(filename, FileMode.Open))
         {
            reader = new BinaryReader(fileStream);
            Int32 magic = reader.ReadInt32();
            if (MAGIC_HEIGHTS != magic)
            {
               logger_.ErrorFormat("bad magic, expected {0} but got {1} for {2}",
                  MAGIC_HEIGHTS, magic, filename);
               return null;
            }
            Int32 tableOffset = reader.ReadInt32();
            Int32 tileOffset = reader.ReadInt32();
            //fileStream.Seek(tableOffset, SeekOrigin.Begin);
            //fileStream.Read(buffer, 0, 512 * 2);
            //fileStream.Seek(tileOffset + (60 * 60 * 2 * 61 * 61) * tileIndex, SeekOrigin.Begin);
            //fileStream.Read(buffer, 0, 512 * 2);
         }
         return null;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void testToolStripMenuItem_Click(object sender, EventArgs e)
      {
         using (Context context = new Context(1))
         {
            using (Socket tester = context.Socket(SocketType.DEALER))
            {
               tester.Connect("tcp://localhost:" + Program.Settings.ListenPort.ToString());
               
               Thread.Sleep(1000);

               MemoryStream stream = new MemoryStream();
               BinaryWriter writer = new BinaryWriter(stream);
               
               // 1 -> means ILocation
               writer.Write((byte)1);

               // encode [N.41.32.17, W.122.5.46, 1700]
               ILocation location = Program.Loader.Create<ILocation>();
               location.FromString("[N.41.32.17, W.122.5.46, 1700]");
               writer.Write(location.Latitude);
               writer.Write(location.Longitude);

               ZMessage message = new ZMessage(stream.ToArray());
               message.Send(tester);
            }
         }
      }
   }
}
